﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Animation;

namespace Xceed.Silverlight.ListBox
{
  internal class AddingItemTransitionAnimation : TransitionAnimation
  {
    internal AddingItemTransitionAnimation( ListBoxAnimationSettings animationSettings )
      : base( animationSettings.GetAddingItemDuration() )
    {
      PowerEase opacityEasing = new PowerEase();
      opacityEasing.EasingMode = EasingMode.EaseIn;
      opacityEasing.Power = 9;

      PowerEase itemsAddedEasing = new PowerEase();
      itemsAddedEasing.EasingMode = EasingMode.EaseIn;
      itemsAddedEasing.Power = 6;

      PowerEase otherItemsEasing = new PowerEase();
      otherItemsEasing.EasingMode = EasingMode.EaseInOut;
      otherItemsEasing.Power = 6;

      m_opacityEasing = opacityEasing;
      m_addedItemsMovementEasing = itemsAddedEasing;
      m_otherItemsMovementEasing = otherItemsEasing;
    }

    internal Orientation Orientation
    {
      get;
      set;
    }

    internal override void AddContainer( ListBoxContainer container)
    {
      //Nothing to do
    }

    internal override LayoutChangeInfo CreateLayoutChangeInfo()
    {
      return new LayoutChangeInfo();
    }

    //This method, called within the DetermineLayoutChanges allows derived class to see the big picture before deciding how
    //to behave with individual listBoxContainer elements.
    internal override void DetermineLayoutChangesCore( Dictionary<ListBoxContainer, LayoutChangeInfo> affectedElements )
    {

      IEnumerable<ListBoxContainer> addedElements = from kvp in affectedElements
                                                   let layoutChangeInfo = kvp.Value
                                                   let addedContainer = kvp.Key
                                                   where layoutChangeInfo.ChangeType == ChangeType.ElementAdded
                                                   select addedContainer;
      // No Element Added
      if( addedElements.Count() <= 0 )
      {
        m_translation = new Point( double.NaN, double.NaN );
      }
      else
      {
        // At least on element 
        Rect addedOperationRect = this.ComputeOperationsRect( addedElements );

        if( this.Orientation == Orientation.Vertical )
        {
          m_translation.X = double.NaN;
          m_translation.Y = addedOperationRect.Height;
        }
        else
        {
          m_translation.X = addedOperationRect.Width;
          m_translation.Y = double.NaN;
        }
      }

    }

    //This method, called within the PreapreContainerCore method, automatically invoked on each "changed" ListBoxContainer detected
    //during the StartTransitionAnimationMethod.
    internal override bool PrepareContainerCore( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      switch( changeInfo.ChangeType )
      {
        case ChangeType.ElementAdded:
          this.PrepareAddedElement( container, changeInfo );
          break;

        case ChangeType.ElementRemoved:
          this.PrepareRemovedElement( container, changeInfo );
          break;

        //If element is changed, don't change a thing.
        case ChangeType.ElementChanged:
          this.PrepareChangedElement( container, changeInfo );
          break;
        default:
          break;
      }

      //systematically return true. All elements flagged as changed are handled in this transition.
      return true;

    }

    //This method, called within the OnRendering method, allows derived classes to update the LayoutChangeInfo associated with ListBoxContainer and
    //also allow for "custom" animations and actions to be performed on the container.
    internal override bool UpdateContainerCore( double normalizedElapsedTime, ListBoxContainer container )
    {

      if( container == null )
        throw new ListBoxInternalException();

      container.UpdateAnimations( normalizedElapsedTime );

      //There is no custom animations in this transition, only those handled by the EasingFunctions.
      //Since I have no custom timeline, I do not have to decide when its "finished". Returning false
      //will just make it so that the animation will proceed for every frame of every item.
      return false;
    }

    //This method, called within the CleanContainer method, allows derived classes to perform any relevant cleanup operation associated with
    //a ListBoxContainer animation. It is there to "compensate" for any action undertaken in the prepare and update abstract methods.
    internal override void CleanContainerCore( ListBoxContainer container )
    {
      if( container == null )
        throw new ListBoxInternalException();

    }

    private void PrepareChangedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      //Removed items in the case of a ItemsAddedTransitionAnimation are items that are shifted out of view
      //by the insertion of new items.

      //Animation for these items is to push them away by the amount of inserted items.
      PositionAnimation positionAnimation = new PositionAnimation();
      RotateAnimation rotatedAnimation = new RotateAnimation();
      ScaleAnimation scaleAnimation = new ScaleAnimation();
      ( ( IAnimation )positionAnimation ).ParentTransition = this;
      ( ( IAnimation )positionAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )rotatedAnimation ).ParentTransition = this;
      ( ( IAnimation )rotatedAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )scaleAnimation ).ParentTransition = this;
      ( ( IAnimation )scaleAnimation ).EasingFunction = m_otherItemsMovementEasing;

      positionAnimation.XChange = changeInfo.XChange;
      positionAnimation.YChange = changeInfo.YChange;
      rotatedAnimation.AngleChange = changeInfo.AngleChange;
      scaleAnimation.ScaleChange = changeInfo.ScaleChange;


      container.AddAnimation( positionAnimation );
      container.AddAnimation( rotatedAnimation );
      container.AddAnimation( scaleAnimation );
    }

    private void PrepareAddedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {

      if( changeInfo == null )
        throw new ListBoxInternalException();

      //In the case of an ItemsAddedTransitionAnimation, added elements are those inserted in the layout.

      //In that case, this class will animate them from the insertion point to their respective position

      Point elementActualPosition = container.ActualComputedPosition;
      OpacityAnimation opacityAnimation = new OpacityAnimation();

      ( ( IAnimation )opacityAnimation ).EasingFunction = m_opacityEasing;
      ( ( IAnimation )opacityAnimation ).ParentTransition = this;
      ( ( IAnimation )opacityAnimation ).AnimationDirection = AnimationDirection.ToReferencePosition;

      opacityAnimation.OpacityChange = -changeInfo.OpacityChange;

      container.AddAnimation( opacityAnimation );

    }

    private void PrepareRemovedElement( ListBoxContainer container, LayoutChangeInfo changeInfo )
    {
      //Removed items in the case of a ItemsAddedTransitionAnimation are items that are shifted out of view
      //by the insertion of new items.

      //Animation for these items is to push them away by the amount of inserted items.
      Point elementActualPosition = container.ActualComputedPosition;
      SizeAnimation sizeAnimation = new SizeAnimation();
      OpacityAnimation opacityAnimation = new OpacityAnimation();
      RotateAnimation rotateAnimation = new RotateAnimation();
      PositionAnimation positionAnimation = new PositionAnimation();

      ( ( IAnimation )sizeAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )sizeAnimation ).ParentTransition = this;

      ( ( IAnimation )opacityAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )opacityAnimation ).ParentTransition = this;
      ( ( IAnimation )opacityAnimation ).AnimationDirection = AnimationDirection.FromReferencePosition;

      ( ( IAnimation )rotateAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )rotateAnimation ).ParentTransition = this;

      ( ( IAnimation )positionAnimation ).EasingFunction = m_otherItemsMovementEasing;
      ( ( IAnimation )positionAnimation ).ParentTransition = this;
      ( ( IAnimation )positionAnimation ).AnimationDirection = AnimationDirection.FromReferencePosition;


      positionAnimation.YChange = m_translation.Y;
      positionAnimation.XChange = m_translation.X;

      sizeAnimation.MaximumHeightChange = m_translation.Y;
      sizeAnimation.MaximumWidthChange = m_translation.X;

      rotateAnimation.AngleChange = changeInfo.AngleChange;
      opacityAnimation.OpacityChange = changeInfo.OpacityChange;

      container.AddAnimation( rotateAnimation );
      container.AddAnimation( opacityAnimation );
      container.AddAnimation( sizeAnimation );
      container.AddAnimation( positionAnimation );
    }

    #region Private Methods
    private Rect ComputeOperationsRect( IEnumerable<ListBoxContainer> elements )
    {
      Rect operationRect = new Rect( double.NaN, double.NaN, double.NaN, double.NaN );
      bool operationOccured = false;
      double lowestOperationX = double.MaxValue;
      double lowestOperationY = double.MaxValue;
      double highestOperationX = double.MinValue;
      double highestOperationY = double.MinValue;

      foreach( ListBoxContainer listBoxContainer in elements )
      {
        Point containerPosition = listBoxContainer.AbsoluteOffset;

        if( ( containerPosition.X != double.NaN ) && ( containerPosition.Y != double.NaN ) )
          continue;

        double elementHeight = listBoxContainer.AbsoluteElementHeight;
        double elementWidth = listBoxContainer.AbsoluteElementWidth;

        if( containerPosition.X < lowestOperationX )
        {
          lowestOperationX = containerPosition.X;
        }

        if( containerPosition.Y < lowestOperationY )
        {
          lowestOperationY = containerPosition.Y;
        }

        if( ( containerPosition.X + elementWidth ) > highestOperationX )
        {
          highestOperationX = containerPosition.X + elementWidth;
        }

        if( ( containerPosition.Y + elementHeight ) > highestOperationY )
        {
          highestOperationY = containerPosition.Y + elementHeight;
        }

        operationOccured = true;
      }

      if( operationOccured )
      {
        operationRect.X = lowestOperationX;
        operationRect.Y = lowestOperationY;
        operationRect.Width = highestOperationX - lowestOperationX;
        operationRect.Height = highestOperationY - lowestOperationY;
      }

      return operationRect;
    }

    #endregion


    #region Private Members

    private IEasingFunction m_opacityEasing;
    private IEasingFunction m_addedItemsMovementEasing;
    private IEasingFunction m_otherItemsMovementEasing;
    private Point m_translation = new Point( 0.0d, 0.0d );

    #endregion
  }
}
